<?php

if (!class_exists('K12Serv_API_Const', false)) {    
    if (file_exists(dirname(__FILE__).'/K12Serv_API_Const.class.php')) require_once dirname(__FILE__).'/K12Serv_API_Const.class.php';
    elseif (class_exists('K12Serv_Loader', false)) K12Serv_Loader::loadClass('K12Serv_API_Const');    
    else throw new Exception('请先加载类 K12Serv_API_Const。');
}

if (!class_exists('K12Serv_API_Client', false)) {
    if (file_exists(dirname(__FILE__).'/K12Serv_API_Client.class.php')) require_once dirname(__FILE__).'/K12Serv_API_Client.class.php';
    elseif (class_exists('K12Serv_Loader', false)) K12Serv_Loader::loadClass('K12Serv_API_Client');
    else throw new Exception('请先加载类 K12Serv_API_Client。');
}

/**
 * 转换器 API 接口类。
 *
 * @author Nio Xiao
 * @package K12Serv
 */
class K12Serv_API_Server
{    
    // 定义错误代码
    const E_INVALID_HTTPMETHOD = K12Serv_API_Const::E_INVALID_HTTPMETHOD;
    const E_INVALID_PARAMETER  = K12Serv_API_Const::E_INVALID_PARAMETER;
    const E_INVALID_API_KEY    = K12Serv_API_Const::E_INVALID_API_KEY;
    const E_INVALID_SIGNATURE  = K12Serv_API_Const::E_INVALID_SIGNATURE;
    const E_INVALID_ACTION     = K12Serv_API_Const::E_INVALID_ACTION;
    const E_INVALID_IP         = K12Serv_API_Const::E_INVALID_IP;
    const E_INTERNAL           = K12Serv_API_Const::E_INTERNAL;
    
    /**
     * 获取当前的 API 调用使用的通信格式，默认为 XML 格式。
     *
     * @return string
     */
    public static function getFormat() {
        return isset($_REQUEST['format']) ? $_REQUEST['format'] : 'xml';
    }
    
    /**
     * 获取当前的 API 调用使用的回调跳转 url 地址。
     *
     * @return string
     */
    public static function getRedirectUrl() {
        return isset($_REQUEST['redirect_url']) ? $_REQUEST['redirect_url'] : '';
    }
    
    /**
     * 验证当前请求是否合法。
     *
     * @param string $api_key 应用与服务通信的公钥
     * @param string $api_secret 应用与服务通信的密钥
     * @param string $client_ips 允许的客户端IP地址，多个IP地址使用竖线“|”分割
     * @param array $valid_params 用于校验签名的参数名组成的数组，空数组则使用所有参数生成签名进行校验。
     * @return boolean
     */
    public static function authenticate($api_key, $api_secret, $client_ips = '', $valid_params = array()){
        self::requireParams(array('api_key', 'signature'), $_SERVER['REQUEST_METHOD']);
        $params = ($_SERVER['REQUEST_METHOD'] == 'POST') ? $_POST : $_GET;
        $signature = $params['signature'];
        // 验证 api_key 是否正确
        if (empty($params['api_key']) || $params['api_key'] != $api_key) {
            self::triggerError(self::E_INVALID_API_KEY, "应用 API key 错误，请确定参数 api_key “{$params['api_key']}” 是否正确");
        }
        // 验证应用服务器 IP 地址是否正确
        if ($client_ips) {
            $remote_addr = self::getRemoteAddr();
            $reg = str_replace('.', '\.', $client_ips);
            $reg = str_replace('*', '[0-9]*', $reg);
            $reg = '/^(' . $reg . ')$/';
            if (!preg_match($reg, $remote_addr)) self::triggerError(self::E_INVALID_IP, "当前应用设置不允许来自 IP 地址（{$remote_addr}）的请求");
        }
        // 验证签名是否正确
        if ($valid_params) {
            $auth_params = array();
            foreach ($valid_params as $name) {
                if (strpos($name, '*') !== false) { //使用 * 模糊匹配参数名
                    $reg = '/'.str_replace('*', '[a-zA-Z0-9_]*', $name).'/';
                    foreach ($params as $k => $v) {
                        if (preg_match($reg, $k)) $auth_params[$k] = $v;
                    }
                } else { //参数名完全匹配
                    if (!isset($params[$name])) continue; //当前没有此参数，跳过
                    $auth_params[$name] = $params[$name];                    
                }
            }
            $params = $auth_params;
            unset($auth_params);
        } else {
            unset($params['signature']);        
        }
        $hash = K12Serv_API_Client::getSignatureHash($params, $api_secret);
        if ($hash != $signature) {
            self::triggerError(self::E_INVALID_SIGNATURE, "签名验证失败，请确定参数 signature 是否正确");
        }
        // 验证通过，返回当前的应用设置
        return true;
    }

    /**
     * 检测必填参数。
     *
     * @param array $params 参数名数组
     * @param string $method HTTP方法
     * @return boolean
     */
    public static function requireParams($params, $method = false){
        settype($params, 'array');
        if ($method == 'POST' || $_SERVER['REQUEST_METHOD'] == 'POST') {
            $var = '_POST';
        } else {
            $var = '_GET';
        }
        foreach ($params as $key) {
            if (!isset($GLOBALS[$var][$key])) {
                $errmsg = "缺少 HTTP {$method} 参数: {$key}";
                self::triggerError(self::E_INVALID_PARAMETER, $errmsg);
            }
        }
        return true;
    }
    
    /**
     * 触发错误，根据客户端设置的格式，输出错误信息并结束运行。
     *
     * @param integer $errcode 错误代码
     * @param string $errmsg 错误信息
     * @param array $extra_data 需要与错误信息一起返回的额外数据。
     */
    public static function triggerError($errcode, $errmsg, $extra_data = array()){
        $data = array(
            'errcode' => $errcode, 
            'errmsg'  => $errmsg,
        );
        if ($extra_data) $data['extra_data'] = $extra_data;
        self::display($data);
        exit;
    }
    
    /**
     * 异常处理。
     *
     * @param Exception $exception
     */
    public static function exceptionHandler($exception) {
        $errmsg = '内部错误';
        if (!empty($_REQUEST['debug'])) $errmsg .= '['.$exception->getMessage().']';
        self::triggerError(self::E_INTERNAL, $errmsg);
    }
    
    /**
     * PHP 错误处理。
     */
    public static function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
        switch ($errno) {
            case E_USER_WARNING:
            case E_USER_NOTICE:
            case E_CORE_WARNING:
            case E_COMPILE_WARNING:
                if (!empty($_REQUEST['debug'])) {
                    self::triggerError(self::E_INTERNAL, "内部错误[{$errstr} - {$errfile}，行号：{$errline}]");
                }
                break;
            case E_USER_ERROR:
            case E_ERROR:
            case E_PARSE:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
                $errmsg = "内部错误";
                if (!empty($_REQUEST['debug'])) $errmsg .= "[{$errstr} - {$errfile}，行号：{$errline}]\n".var_export($errcontext, true)."]";
                self::triggerError(self::E_INTERNAL, $errmsg);
            case E_NOTICE:
            case E_WARNING:
            default:
                break;
        }
    }    
    
    /**
     * 获取返回数据格式化之后的字符串。
     *
     * @param array $data 需要输出的数据数组
     * @return string
     */
    public static function fetch($data) {
        $result = '';
        $format = self::getFormat();
        switch ($format) {
            case 'php':
                $result .= serialize($data);
                break;
            case 'redirect':
                $url = self::getRedirectUrl();
                if (!$url) die('未设置返回值的回调地址参数 redirect_url。');
                $pos = strpos($url, '?') === false ? '?' : '&';
                $url .= $pos.http_build_query($data);
                $result = $url;
                break;
            case 'json':
                $result .= json_encode($data);
                break;
            case 'xml':
            default:
                $result .= '<?xml version="1.0" encoding="utf-8"?>'."\n";
                $result .= '<k12>'."\n";
                $result .= self::toXML($data);
                $result .= '</k12>';
                break;
        }
        return $result;
    }
    
    /**
     * 显示输出返回数据并结束运行。
     *
     * @param array $data 返回数据组成的数组。
     */
    public static function display($data) {
        $format = self::getFormat();
        $result = self::fetch($data);
        if ($format == 'redirect') {
            header('Location: '.$result);
        } else{
            if ($format == 'xml') header("Content-type: text/xml");
            elseif ($format == 'json') header('Content-type: text/json');
            else header('Content-type: text/html'); //$format == 'php' 或其它
            echo $result;
        }
        exit;
    }
    
    /**
     * 获取客户端地址。
     *
     * @return string
     */
    public static function getRemoteAddr()
    {
        $remote_addr = (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || $_SERVER['HTTP_X_FORWARDED_FOR'] == '127.0.0.1')
            ? $_SERVER['REMOTE_ADDR']
            : $_SERVER['HTTP_X_FORWARDED_FOR'];
        $arr = explode(',', $remote_addr);
        return trim($arr[0]);
    }

    /**
     * 将数组转换成 XML 数据字符串。
     *
     * @param array $array
     * @return string
     */
    private static function toXML($array) {
        $xml = '';
        foreach ($array as $k => $v) {
            $k = is_numeric($k) ? 'item' : $k;
            $xml .= "<$k>";
            if (is_array($v)) {
                $xml .= self::toXML($v);
            } else {
                $xml .= htmlspecialchars($v);
            }
            $xml .= "</$k>\n";
        }
        return $xml;
    }    
}

// 设置错误处理函数
set_exception_handler(array('K12Serv_API_Server', 'exceptionHandler'));
set_error_handler(array('K12Serv_API_Server', 'errorHandler'));
